/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.fish;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.core.HasParentPotentialHint;
import com.onegravity.sudoku.solver.core.IndirectHint;
import com.onegravity.sudoku.solver.core.Potential;
import com.onegravity.sudoku.solver.core.Rule;
import com.onegravity.sudoku.solver.data.Block;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.ExtraRegionExtended;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.util.HtmlLoader;

/**
 * Locking hint (Pointing, Claiming, X-Wing, Swordfish or Jellyfish)
 */
public class LockingHint extends IndirectHint implements Rule, HasParentPotentialHint {

	private static final long serialVersionUID = 46393987314482276L;
	
	private final Cell[] cells;
    private final int value;
    private final Map<Cell, BitSet> highlightPotentials;
    private final Region[] regions;

    private boolean isFinnedFish = false;
    private boolean isSashimiFish = false;
    private Map<Cell, BitSet> secondaryHighlightPotentials = null;

    public LockingHint(Cell[] cells, int value, Map<Cell, BitSet> highlightPotentials, Map<Cell, BitSet> secondaryHighlightPotentials, 
			   boolean isSashimiFish, Map<Cell, BitSet> removePotentials, Region... regions) {
		this(cells, value, highlightPotentials, removePotentials, regions);
		this.secondaryHighlightPotentials = secondaryHighlightPotentials;
		this.isFinnedFish = true;
		this.isSashimiFish = isSashimiFish;
    }

    public LockingHint(Cell[] cells, int value, Map<Cell, BitSet> highlightPotentials, Map<Cell, BitSet> removePotentials, Region... regions) {
        super( removePotentials );
        this.cells = cells;
        this.value = value;
        this.highlightPotentials = highlightPotentials;
        this.regions = regions;
        this.secondaryHighlightPotentials = null;
    }
    
    @Override
    public Collection<Cell> getSelectedCells(HintType hintType) {
        return null;
    }

    @Override
    public Map<Cell, BitSet> getSelectedPotentials(HintType hintType, int viewNum) {
        return (hintType!=HintType.SMALL_CLUE) ? highlightPotentials : null;
    }

    public Map<Cell, BitSet> getSecondarySelectedPotentials(HintType hintType, int viewNum) {
    	return (hintType==HintType.SMALL_CLUE) ? null : (secondaryHighlightPotentials==null) ? super.getSecondarySelectedPotentials(hintType, viewNum) : secondaryHighlightPotentials;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType hintType) {
        return (hintType==HintType.HINT) ? super.getRemovedPotentials(hintType) : null;
    }

    @Override
    public Region[] getRegions(HintType hintType) {
        return this.regions;
    }

    @Override
    public double getDifficulty() {
        int degree = regions.length / 2;
        switch (degree) {
        case 1: {
        	// pointing & claiming
        	double add4ExtraRegion = (regions[0] instanceof ExtraRegionExtended) || (regions[1] instanceof ExtraRegionExtended) ? 0.2 : 0.0;
            return ((regions[0] instanceof Block) ? 2.6 : 2.8) + add4ExtraRegion; // Pointing=2.6, Claiming=2.8
        }
        case 2:
        	 // X-Wing
            return isSashimiFish ? 3.5 : isFinnedFish ? 3.4 : 3.2;
        case 3:
        	// Swordfish
            return isSashimiFish ? 4.2 : isFinnedFish ? 4.0 : 3.8;
        default:
        	// Jellyfish
            return isSashimiFish ? 5.6 : isFinnedFish ? 5.4 : 5.2;
        }
    }

    @Override
    public String getName() {
        int degree = regions.length / 2;
        switch (degree) {
        case 1:
        	return (regions[0] instanceof Block) ? HintResource.POINTING.getName(regions[0].toString()) : HintResource.CLAIMING.getName(regions[0].toString());
        case 2:
            return isSashimiFish ? HintResource.SASHIMI_XWING.getName() : isFinnedFish ? HintResource.FINNED_XWING.getName() : HintResource.XWING.getName();
        case 3:
        	return isSashimiFish ? HintResource.SASHIMI_SWORDFISH.getName() : isFinnedFish ? HintResource.FINNED_SWORDFISH.getName() : HintResource.SWORDFISH.getName();
        case 4:
            return isSashimiFish ? HintResource.SASHIMI_JELLYFISH.getName() : isFinnedFish ? HintResource.FINNED_JELLYFISH.getName() : HintResource.JELLYFISH.getName();
        }
        return null;
    }

    @Override
    public Uri getLink() {
        int degree = regions.length / 2;
        switch (degree) {
        case 1:
        	return (regions[0] instanceof Block) ? HintResource.POINTING.getUrl() : HintResource.CLAIMING.getUrl();
        case 2:
            return isSashimiFish ? HintResource.SASHIMI_XWING.getUrl() : isFinnedFish ? HintResource.FINNED_XWING.getUrl() : HintResource.XWING.getUrl();
        case 3:
        	return isSashimiFish ? HintResource.SASHIMI_SWORDFISH.getUrl() : isFinnedFish ? HintResource.FINNED_SWORDFISH.getUrl() : HintResource.SWORDFISH.getUrl();
        case 4:
            return isSashimiFish ? HintResource.SASHIMI_JELLYFISH.getUrl() : isFinnedFish ? HintResource.FINNED_JELLYFISH.getUrl() : HintResource.JELLYFISH.getUrl();
        }
        return null;
	}

    public Collection<Potential> getRuleParents(Grid initialGrid, Grid currentGrid) {
        Collection<Potential> result = new ArrayList<Potential>();
        // Add any potential of first region that are not in second region
        for (int i = 0; i < regions.length; i+= 2) {
            for (int pos1 = 0; pos1 < 9; pos1++) {
                Cell cell = regions[i].getCell(pos1);
                Cell initCell = initialGrid.getCell(cell.getX(), cell.getY());
                if (initCell.hasPotentialValue(value) && !cell.hasPotentialValue(value)) {
                    boolean isInRegion2 = false;
                    for (int j = 1; j < regions.length; j+= 2) {
                        for (int pos2 = 0; pos2 < 9; pos2++) {
                            Cell other = regions[j].getCell(pos2);
                            if (other.equals(cell))
                                isInRegion2 = true;
                        }
                    }
                    if (!isInRegion2)
                        result.add(new Potential(cell, value, false));
                }
            }
        }
        return result;
    }

    @Override
    public String getHintHtml(HintType type) {
        HintResource hintRes = null;
        int degree = regions.length / 2;
        switch (degree) {
        case 1: {
            hintRes = (regions[0] instanceof Block) ? HintResource.POINTING : HintResource.CLAIMING;
            String result = HtmlLoader.loadHtml(hintRes.hint(type));
            String valueName = Integer.toString(value);
            String firstRegion = regions[0].toFullString();
            String secondRegion = regions[1].toFullString();
            String ruleName = getName();
            return HtmlLoader.format(result, valueName, firstRegion, secondRegion, ruleName);
        }
        case 2:
            hintRes = isSashimiFish ? HintResource.SASHIMI_XWING : isFinnedFish ? HintResource.FINNED_XWING : HintResource.XWING;
            break;
        case 3:
        	hintRes = isSashimiFish ? HintResource.SASHIMI_SWORDFISH : isFinnedFish ? HintResource.FINNED_SWORDFISH : HintResource.SWORDFISH;
        	break;
        case 4:
        	hintRes = isSashimiFish ? HintResource.SASHIMI_JELLYFISH : isFinnedFish ? HintResource.FINNED_JELLYFISH : HintResource.JELLYFISH;
        	break;
        }
        String result = HtmlLoader.loadHtml(hintRes.hint(type));
        final int[] numberIds = new int[] {R.string.two, R.string.three, R.string.four, R.string.five, R.string.six, R.string.seven, R.string.eight};
        String ruleName = getName();
        String valueName = Integer.toString(value);
        String degreeName = HintResource.getString(numberIds[degree - 2]);
        String firstRegion = regions[0].toString();
        String secondRegion = regions[1].toString();
        Cell[] emptyCells = new Cell[0]; 
        Cell[] removableCells = getRemovablePotentials().keySet().toArray(emptyCells);

        // this code is for finned fishes only
        Cell[] finCells = isFinnedFish ? secondaryHighlightPotentials.keySet().toArray(emptyCells) : emptyCells;
        int finLength = isFinnedFish ? finCells.length : 0;
        String finNumber = (isFinnedFish && finLength>=2) ? HintResource.getString(numberIds[finLength-2]) : ""; //$NON-NLS-1$
        String finEnding = (isFinnedFish && finLength>=2) ? "s" : ""; //$NON-NLS-1$ //$NON-NLS-2$
        String[] tmp = ruleName.split("\\s"); //$NON-NLS-1$
        String ruleNameStart = tmp[0];
        String ruleNameEnd = (tmp.length>1) ? tmp[1] : ""; //$NON-NLS-1$
        
        return HtmlLoader.format(result, ruleName, valueName, degreeName, firstRegion, secondRegion,
        						 Cell.toFullString(removableCells), Cell.toFullString(finCells),
        						 finNumber, finEnding, ruleNameStart, ruleNameEnd);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(getName());
        builder.append(": "); //$NON-NLS-1$
        builder.append(Cell.toFullString(this.cells));
        builder.append(": "); //$NON-NLS-1$
        builder.append(value);
        if (regions != null) {
            if (regions.length == 2) {
                builder.append(" in "); //$NON-NLS-1$
                builder.append(regions[0].toString());
                builder.append(" and "); //$NON-NLS-1$
                builder.append(regions[1].toString());
            } else if (regions.length >= 4 && regions.length % 2 == 0) {
                builder.append(" in " + (regions.length / 2) + " "); //$NON-NLS-1$ //$NON-NLS-2$
                builder.append(regions[0].toString());
                builder.append("s and " + (regions.length / 2) + " "); //$NON-NLS-1$ //$NON-NLS-2$
                builder.append(regions[1].toString());
                builder.append("s"); //$NON-NLS-1$
            }
        }
        return builder.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof LockingHint)) return false;
        LockingHint other = (LockingHint)o;
        if (this.value != other.value) return false;
        if (this.cells.length != other.cells.length) return false;
        if (this.isFinnedFish != other.isFinnedFish) return false;
        if (this.isSashimiFish != other.isSashimiFish) return false;
        return Arrays.asList(this.cells).containsAll(Arrays.asList(other.cells));
    }

    @Override
    public int hashCode() {
        int result = 0;
        for (Cell cell : cells)
            result ^= cell.hashCode();
        result ^= value;
        return result;
    }

}